﻿#include "Application.h"
#include "Framework/ApplicationSubsystem.h"

#include "Path/Paths.h"
#include <memory>
#include "Object/Object.h"
#include "Object/GarbageCollector.h"
#include "Scene/Scene.h"

#include "Output/Console.h"

#include "Framework/SceneManager.h"

#include "Platform/PlatformInterface.h"

#include "Framework/Tickable.h"

#include "Widgets/WindowManager.h"

#include "Render/RenderContext.h"

#include "Async/Async.h"

#include "ScriptRuntime/ScriptSubsystem.h"
#include "Time/CPUTimeCounter.h"

static RApplication* ApplicationInstance=nullptr;

RApplication::RApplication()
{
    assert(ApplicationInstance==nullptr && "Only allow one application exist at same time");
    ApplicationInstance=this;

    GC= new CGarbageCollector();

    //initialize reflection types
    {
        for(CType* NativeType: CType::GetTypeTable().GetAllNativeTypes())
        {
            if(NativeType->PostConstructFunction)
            {
                NativeType->PostConstructFunction();
            }
        }
    }
}


RApplication::~RApplication()
{
    DeinitializeSubsystems();

    ApplicationInstance=nullptr;

    delete GC;

    CAsyncSystem::Shutdown();

    WH::PrintLine(U"Application Deleted!");
}

RApplication* RApplication::GetApplication()
{
    return ApplicationInstance;
}

void RApplication::Initialize(const SApplicationArgument& Arguments)
{
    InitializeArguments=Arguments;

    SetLogicFrameLimit(Arguments.FrameLimit);

    SPaths::SetExecutablePath(InitializeArguments.ExecutablePath);
    if(InitializeArguments.ContentDirectory.HasValue())
    {
        SPaths::SetContentDirectory(InitializeArguments.ContentDirectory.GetValue());
    }
    else 
    {
        SPaths::SetContentDirectory(InitializeArguments.ExecutablePath.Parent() / U"Content");
    }



    CAsyncSystem::Initialize(InitializeArguments.WorkThreadNum);

    InitializeSubsystems();

    WH::PrintLine(U"Application initialized!");
}

int RApplication::Run()
{
    while (true) 
    {
        if(bRunning==false)
        {
            break;
        }

        if(RunOnce()==-1)
        {
            break;
        }
    }    

    return 0;
}


int RApplication::RunOnce()
{
    if(bRunning==false)
    {
        return -1;
    }

    BEGIN_FRAME_TIME_COUNT(LogicThread);

    DispatchEvent();
   
    BEGIN_CODE_BLOCK_TIME_COUNT(U"帧率限制等待");

    auto Current = std::chrono::steady_clock::now();
    float ElapsedTime=0; //unit: second
    
    if(LastFrameTime.HasValue())
    {   
        //计算上一帧到当前帧的时间间隔，单位为微秒
        auto ElapsedTimeMicroS = std::chrono::duration_cast<std::chrono::microseconds>(Current - LastFrameTime.GetValue()).count();
        
        //限制帧率
        if(FrameLimit!=0)
        {
            float TargetFrameTime=1.0f/FrameLimit; //unit: second
            uint32_t TargetFrameTimeMicroS=TargetFrameTime*1000000;

            //由于睡眠时间不精确，所以循环等待直到时间到达
            while(ElapsedTimeMicroS<=TargetFrameTimeMicroS)
            {
                std::this_thread::yield();// sleep_for(std::chrono::microseconds(1));
                Current = std::chrono::steady_clock::now();
                ElapsedTimeMicroS=std::chrono::duration_cast<std::chrono::microseconds>(Current - LastFrameTime.GetValue()).count();
            }
            
        }
        
        ElapsedTime = ElapsedTimeMicroS / 1000000.0f;

    }
    else
    {
        //第一帧，时间间隔设为一个很小的值，不能为0，防止计算FPS时除0错误
        ElapsedTime=0.00001f;
    }

    LastFrameTime=Current; 
    FramePerSecond=1/ElapsedTime;

    END_CODE_BLOCK_TIME_COUNT();

    if(bPrintFPS)
    {
        WH::PrintLine(U"逻辑帧数:{} 渲染帧数{}" , FramePerSecond,RRenderContext::Get()->GetRenderFramePerSecond());
    }


    RunMainThreadTasks();

    auto SceneSubsystem=GetSubsystem<RSceneManager>();
    SceneSubsystem->Tick(ElapsedTime);
    TickableManager.Tick(ElapsedTime);
    
    //try GC
    if(bEnableGC)
    {
        GCTimer+=ElapsedTime*1000;
        if(GCTimer>GCInterval )
        {
            GCTimer=0; 
            GC->GarbageCollect();
        }
    }

    //绘制窗口所有内容,包括控件和三维场景
    auto WindowManager=GetSubsystem<RWindowManager>();
    WindowManager->PaintWindows();

    END_FRAME_TIME_COUNT(LogicThread);

    return 0;
}

void RApplication::Exit()
{
    bRunning=false;

    WH::PrintLine(U"Application Exit!");
}

void RApplication::RunMainThreadTasks()
{
    CODE_BLOCK_TIME_COUNT(U"主线程任务");
    CAsyncSystem::Get()->DoMainThreadWork();
}

void RApplication::DispatchEvent()
{
    
    CODE_BLOCK_TIME_COUNT(U"分发系统事件");


    auto WindowManager=GetSubsystem<RWindowManager>();

    //dispatch events
    while(true)
    {
        TOptional<SystemEvent> MessageOptional= IPlatformInterface::GetPlatformInterface()->PopEvents();
        if(!MessageOptional.HasValue())
        {
            break;
        }
        auto Message=MessageOptional.GetValue();

        //从消息中创建事件
        std::visit([this,WindowManager](auto&& Arg) {
            using T = std::decay_t<decltype(Arg)>;
            if constexpr (std::is_same_v<T, SSystemKeyboardKeyDown>)
            {

                SKeyboardEvent::SetGlobalKeyState(Arg.Key,EKeyState::Pressed);

                if(!Shortcuts.Empty())
                {
                    SShortcutKey Key;
                    Key.Key=Arg.Key;
                    Key.bAnyAltDown=SKeyboardEvent::IsAnyAltDown();
                    Key.bAnyCtrlDown=SKeyboardEvent::IsAnyCtrlDown();
                    Key.bAnyShiftDown=SKeyboardEvent::IsAnyShiftDown();
                    Key.bAnySystemDown=SKeyboardEvent::IsAnySystemDown();

                    if(Shortcuts.Contains(Key))
                    {
                        Shortcuts[Key].Broadcast();
                        return;
                    };
                }

                SKeyboardEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Key=Arg.Key;
                Event.KeyState=EKeyState::Pressed;

                WindowManager->ProcessKeyboardEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemKeyboardKeyUp>)
            {
                SKeyboardEvent::SetGlobalKeyState(Arg.Key,EKeyState::Released);

                SKeyboardEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Key=Arg.Key;
                Event.KeyState=EKeyState::Released;

                WindowManager->ProcessKeyboardEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemTextInput>)
            {
                STextInputEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Text=Arg.Text;
                WindowManager->ProcessTextInputEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemMouseButtonDown>)
            {
                switch(Arg.Button)
                {
                    case EMouseButton::Left:
                        SMouseButtonEvent::State.bIsLeftButtonPressed=true;
                        break;
                    case EMouseButton::Right:
                        SMouseButtonEvent::State.bIsRightButtonPressed=true;
                        break;
                    case EMouseButton::Middle:
                        SMouseButtonEvent::State.bIsMiddleButtonPressed=true;
                        break;
                    default:
                        break;
                }

                SMouseButtonEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Button=Arg.Button;
                Event.Type=EMouseEventType::ButtonDown;
                WindowManager->ProcessMouseEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemMouseButtonUp>)
            {
                switch(Arg.Button)
                {
                    case EMouseButton::Left:
                        SMouseButtonEvent::State.bIsLeftButtonPressed=false;
                        break;
                    case EMouseButton::Right:
                        SMouseButtonEvent::State.bIsRightButtonPressed=false;
                        break;
                    case EMouseButton::Middle:
                        SMouseButtonEvent::State.bIsMiddleButtonPressed=false;
                        break;
                    default:
                        break;
                }

                SMouseButtonEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Button=Arg.Button;
                Event.Type=EMouseEventType::ButtonUp;
                WindowManager->ProcessMouseEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemMouseMove>)
            {
                SMouseButtonEvent Event;
                Event.MoveDistance=Arg.Delta;
                Event.WindowID = Arg.WindowID;
                Event.Type=EMouseEventType::Move;

                SMouseButtonEvent::State.ScreenPosition=Arg.ScreenMousePosition;
                WindowManager->ProcessMouseEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemMouseWheelRoll>)
            {
                SMouseButtonEvent Event;
                Event.Type=EMouseEventType::WheelRoll;
                Event.WindowID = Arg.WindowID;
                Event.WheelDistance=Arg.WheelDistance;

                WindowManager->ProcessMouseEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemDragFileOver>)
            {
                SMouseButtonEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Type=EMouseEventType::DragFileOver;
                Event.FilePaths=Arg.FilePaths;

                WindowManager->ProcessMouseEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemDropFiles>)
            {
                SMouseButtonEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Type=EMouseEventType::DropFile;
                Event.FilePaths=Arg.FilePaths;

                WindowManager->ProcessMouseEvent(Event);
            }


            else if constexpr(std::is_same_v<T, SSystemWindowResize>)
            {
                SWindowEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Type=EWindowEventType::Resize;
                Event.WindowSize=Arg.NewSize;
                WindowManager->ProcessWindowEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemWindowClose>)
            {
                SWindowEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Type=EWindowEventType::Close;
                WindowManager->ProcessWindowEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemWindowLostFocus>)
            {
                SWindowEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Type=EWindowEventType::LostFocus;
                Event.AnotherWindowID=Arg.FocusedWindowID;
                WindowManager->ProcessWindowEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemWindowGotFocus>)
            {
                SWindowEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Type=EWindowEventType::GotFocus;
                WindowManager->ProcessWindowEvent(Event);
            }
            else if constexpr(std::is_same_v<T, SSystemClickDisabledWindow>)
            {
                SWindowEvent Event;
                Event.WindowID = Arg.WindowID;
                Event.Type=EWindowEventType::ClickDisabled;
                WindowManager->ProcessWindowEvent(Event);
            }
            else
            {
                assert(false && "Unknown event type");
            }
          
        }, Message);



    }

}

void RApplication::InitializeSubsystems()
{
    if(!SubsystemManager)
    {
        SubsystemManager=NewObject<RSubsystemManager>(this);
    }
    SubsystemManager->InitializeSubsystems(RApplicationSubsystem::StaticClass(),this);
    
}

void RApplication::DeinitializeSubsystems()
{
    SubsystemManager->DeinitializeSubsystems();

}

void RApplication::RegisterTickable(ITickable *InTickable)
{
    return TickableManager.Register(InTickable);
}

void RApplication::UnregisterTickable(ITickable *InTickable)
{
    TickableManager.Unregister(InTickable);
}
